/******************************************************************************
	This file is a part of the Snapshot Bioinformatics Project

	Copyright (C) 2012-2013 Jarom Schow

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/

#include "AppSettings.h"

#include <QtCore/QCoreApplication>
#include <QtCore/QFile>

#include <QtXml/QDomDocument>

#include "Core.h"

class AppSettingsDom {
public:
	AppSettingsDom( const QString &appName );
	~AppSettingsDom();

	bool	mapToDom( const QSettings::SettingsMap &map );
	bool	domToMap( QSettings::SettingsMap &map );

	bool	read( QIODevice &dev );
	bool	write( QIODevice &dev );

private:

	bool		addValue( const QString &path, const QVariant &value );
	QDomNode	findGroup( const QString &name );

	bool		groupToMap( const QDomElement &group, QSettings::SettingsMap &map, const QString &parentGroup );
	bool		valueToMap( const QDomElement &value, QSettings::SettingsMap &map, const QString &parentGroup );

	QString			m_appName;
	QDomDocument	m_doc;
	QDomElement		m_root;

	QMap<QString,QDomNode>	m_groups;
};

const QString	c_dbSettings( "DBSettings" );
const QString	c_dbType( "Type" );
const QString	c_dbHost( "Host" );
const QString	c_dbPort( "Port" );
const QString	c_dbUserName( "UserName" );
const QString	c_dbPassword( "Password" );
const QString	c_dbDatabase( "Database" );
const QString	c_dbConfigFile( "ConfigFile" );

const QString	c_dbPostgreSQL( "PostgreSQL" );
const QString	c_dbMySQL( "MySQL" );
const QString	c_dbMySQLEmbedded( "MySQLEmbedded" );

/**
**/
bool AppSettings::loadDBSettings( Bio::SqlServerSettings &dbSettings )
{
	QSettings	settings;
	QString		typeStr;
	bool		status = true;

	settings.beginGroup( c_dbSettings );

	typeStr = settings.value( c_dbType ).toString();

	if( typeStr == c_dbMySQLEmbedded ) {
		dbSettings.setServerType( Bio::SqlServerSettings::MySQLEmbedded );
		dbSettings.setConfigurationFile( settings.value( c_dbConfigFile ).toString() );
	}
	else {
		if( typeStr == c_dbMySQL )
			dbSettings.setServerType( Bio::SqlServerSettings::MySQL );
		else {
			if( typeStr.isEmpty() )
				status = false;
			dbSettings.setServerType( Bio::SqlServerSettings::PostgreSQL );
		}

		dbSettings.setHostName( settings.value( c_dbHost, "127.0.0.1" ).toString() );
		dbSettings.setPort( settings.value( c_dbPort, 3306 ).toInt() );
		dbSettings.setUserName( settings.value( c_dbUserName ).toString() );
		dbSettings.setPassword( settings.value( c_dbPassword ).toString() );
	}

	dbSettings.setDatabaseName( settings.value( c_dbDatabase ).toString() );

	return status;
}

/**
**/
void AppSettings::saveDBSettings( const Bio::SqlServerSettings &dbSettings )
{
	QSettings	settings;

	Bio::SqlServerSettings::ServerType	type = dbSettings.getServerType();

	settings.beginGroup( c_dbSettings );

	if( type == Bio::SqlServerSettings::MySQLEmbedded ) {
		settings.setValue( c_dbType, c_dbMySQLEmbedded );
		settings.setValue( c_dbConfigFile, dbSettings.getConfigurationFile() );
	}
	else {
		if( type == Bio::SqlServerSettings::PostgreSQL )
			settings.setValue( c_dbType, c_dbPostgreSQL );
		else if( type == Bio::SqlServerSettings::MySQL )
			settings.setValue( c_dbType, c_dbMySQL );

		settings.setValue( c_dbHost, dbSettings.getHostName() );
		settings.setValue( c_dbPort, dbSettings.getPort() );
		settings.setValue( c_dbUserName, dbSettings.getUserName() );
		settings.setValue( c_dbPassword, dbSettings.getPassword() );
	}

	settings.setValue( c_dbDatabase, dbSettings.getDatabaseName() );
}

/**
**/
bool AppSettings::read( QIODevice &device, QSettings::SettingsMap &map )
{
	AppSettingsDom	dom( qApp->applicationName() );

	if( !dom.read( device ) )
		return false;

	if( !dom.domToMap( map ) )
		return false;

	return true;
}

/**
**/
bool AppSettings::write( QIODevice &device, const QSettings::SettingsMap &map )
{
	AppSettingsDom	dom( qApp->applicationName() );

	if( !dom.mapToDom( map ) )
		return false;

	if( !dom.write( device ) )
		return false;

	return true;
}

/**
**/
AppSettingsDom::AppSettingsDom( const QString &appName ) : m_appName( appName )
{
}

/**
**/
AppSettingsDom::~AppSettingsDom()
{
}

/**
**/
bool AppSettingsDom::mapToDom( const QSettings::SettingsMap &map )
{
	QMapIterator<QString, QVariant>	mapIt( map );

	m_doc = QDomDocument();
	m_root = m_doc.createElement( "Settings" );
	m_root.setAttribute( "Application", m_appName );
	m_doc.appendChild( m_root );

	while( mapIt.hasNext() ){
		mapIt.next();

		if( !addValue( mapIt.key(), mapIt.value() ) )
			return false;
	}

	return true;
}

/**
**/
bool AppSettingsDom::domToMap( QSettings::SettingsMap &map )
{
	QDomNode	node = m_doc.firstChild();

	while( !node.isNull() ){
		QDomElement	elem = node.toElement();

		if( !elem.isNull() ){
			if( elem.tagName() == "Settings" ){
				if( !groupToMap( elem, map, QString::null ) )
					return false;
			}
			else {
				Bio::Core::warning( QString( "Unknown element in settings file '%1', expected 'Settings'." ).arg( elem.tagName() ) );
			}
		}

		node = node.nextSibling();
	}

	return true;
}

/**
**/
bool AppSettingsDom::read( QIODevice &dev )
{
	QString	errMsg;
	int		errLine, errCol;

	if( m_doc.setContent( &dev, &errMsg, &errLine, &errCol ) )
		return true;

	Bio::Core::error( QString( "Error in settings file (%1:%2): %3" ).arg( errLine ).arg( errCol ).arg( errMsg ) );

	return false;
}

/**
**/
bool AppSettingsDom::write( QIODevice &dev )
{
	qint64	nBytes = dev.write( m_doc.toByteArray() );

	return (nBytes > 0);
}

/**
**/
bool AppSettingsDom::addValue( const QString &path, const QVariant &value )
{
	int			idx = path.lastIndexOf( '/' );
	QDomNode	parent;
	QString		name;

	if( idx > 0 ){
		parent = findGroup( path.left( idx ) );
		name = path.mid( idx + 1 );
	}
	else {
		parent = m_root;
		name = path;
	}

	if( parent.isNull() || name.isEmpty() )
		return false;

	QDomElement	elem = m_doc.createElement( "Value" );

	parent.appendChild( elem );
	elem.setAttribute( "Name", name );
	elem.setAttribute( "Type", value.typeName() );
	
	QDomText	text = m_doc.createTextNode( value.toString() );

	elem.appendChild( text );

	return true;
}

/**
**/
QDomNode AppSettingsDom::findGroup( const QString &name )
{
	QDomNode	group = m_groups.value( name, QDomNode() );

	if( !group.isNull() )
		return group;

	int	idx = name.lastIndexOf( '/' );

	if( idx < 0 ){
		QDomElement	elem = m_doc.createElement( "Group" );

		elem.setAttribute( "Name", name );
		m_root.appendChild( elem );
		m_groups[name] = elem;

		return elem;
	}

	QDomNode	parent = findGroup( name.left( idx ) );

	if( parent.isNull() )
		return parent;

	QDomElement	elem = m_doc.createElement( "Group" );

	elem.setAttribute( "Name", name.mid( idx + 1 ) );
	parent.appendChild( elem );
	m_groups[name] = elem;

	return elem;
}

/**
**/
bool AppSettingsDom::groupToMap( const QDomElement &group, QSettings::SettingsMap &map, const QString &parentGroup )
{
	QDomNode	node = group.firstChild();

	while( !node.isNull() ){
		QDomElement	elem = node.toElement();

		if( !elem.isNull() ){
			if( elem.tagName() == "Group" ){
				QString	groupPath = parentGroup + elem.attribute( "Name" ) + '/';

				if( !groupToMap( elem, map, groupPath ) )
					return false;
			}
			else if( elem.tagName() == "Value" ){
				if( !valueToMap( elem, map, parentGroup ) )
					return false;
			}
			else {
				Bio::Core::warning( QString( "Unknown element in settings file '%1', expected 'Group' or 'Value'." ).arg( elem.tagName() ) );
			}
		}

		node = node.nextSibling();
	}

	return true;
}

/**
**/
bool AppSettingsDom::valueToMap( const QDomElement &value, QSettings::SettingsMap &map, const QString &parentGroup )
{
	QString	name = value.attribute( "Name" );

	if( name.isEmpty() ){
		Bio::Core::error( "No name specified for 'Value' element in settings file." );
		return false;
	}

	QString	typeStr = value.attribute( "Type" );

	if( typeStr.isEmpty() ){
		Bio::Core::error( "No type specified for 'Value' element in settings file." );
		return false;
	}

	QVariant::Type	type = QVariant::nameToType( typeStr.toLatin1().constData() );

	if( type == QVariant::Invalid ){
		Bio::Core::error( QString( "Invalid type specified for 'Value' element in settings file: '%1'." ).arg( typeStr ) );
		return false;
	}

	QVariant	var( value.text() );

	if( var.convert( type ) )
		map[parentGroup + name] = var;
	else
		Bio::Core::warning( QString( "Failed to convert value '%1' to type '%2' in settings file." ).arg( value.text() ).arg( typeStr ) );

	return true;
}

/**
**
bool AppSettings::loadFromFile( const QString &filename )
{
	QFile	file( filename );

	if( !file.open( QIODevice::ReadOnly ) )
		return false;

	bool			status = true;
	int				errLine, errCol;
	QString			errMsg;
	QDomDocument	doc;

	status = doc.setContent( &file, &errMsg, &errLine, &errCol );

	file.close();

	if( !status ){
		Core::error( QString( "%1(%2:%3): %4" ).arg( filename ).arg( errLine ).arg( errCol ).arg( errMsg ) );
		return false;
	}

	QDomNode	node = doc.firstChild();

	while( !node.isNull() ){
		QDomElement	elem = node.toElement();

		if( !elem.isNull() ){
			if( elem.tagName() == "AppSettings" ){
				if( !parseSettings( elem ) )
					return false;
			}
		}

		node = node.nextSibling();
	}

	return true;
}

/**
**
bool AppSettings::saveToFile( const QString &filename ) const
{
	QDomDocument	doc;
	QDomElement		root = doc.createElement( "AppSettings" );

	doc.appendChild( root );

	QHashIterator<QString,QString>	valIt( m_values );

	while( valIt.hasNext() ){
		valIt.next();

		QDomElement	val = doc.createElement( valIt.key() );
		QDomText	text = doc.createTextNode( valIt.value() );

		root.appendChild( val );
		val.appendChild( text );
	}

	QFile	file( filename );

	if( !file.open( QIODevice::WriteOnly ) )
		return false;

	file.write( doc.toByteArray() );

	return true;
}

/**
**
bool AppSettings::parseSettings( const QDomElement &root )
{
	QDomNode	node = root.firstChild();

	while( !node.isNull() ){
		QDomElement	elem = node.toElement();

		if( !elem.isNull() )
			m_values[elem.tagName()] = elem.text();

		node = node.nextSibling();
	}

	return true;
}
*/